#ifndef VERTEX_SHADER_H_
#define VERTEX_SHADER_H_
#include <iostream>
#include <cmath>
#include "adm_matrix.h"
#include "adm_shader.h"
#include "adm_element.h"
using namespace std;
namespace Andromeda {
  typedef enum  {
    AdjacentPoint = 0,
    ScatterPoint  = 1
  } LineFormat ;

  class VertexShader : public Shader {
  public :
    VertexShader() {
      name_      = "VertexShader";
      in_vertex_ = 0;
      out_vertex_= 0;
      transMat_  = mat4(1.0); //Unit Matrix
    }
    ~VertexShader() {
      delete[] in_vertex_;
      delete[] out_vertex_;
    } 
    virtual string getName() {
      return name_;
    }
    /////////////////////////////////////////////////////////////////
    // Load the vertex from point array
    // float* xyz_ is the point array. 
    // length is point number
    /////////////////////////////////////////////////////////////////
    bool loadVertex(float* xyz_, int length){
       if (length< 10000) {
         num_ = length;
         in_vertex_ = new vec3[length];
         out_vertex_= new vec4[length];
         for (int i=0; i<length; i++) {
           in_vertex_[i][0] = xyz_[3*i + 0];
           in_vertex_[i][1] = xyz_[3*i + 1];
           in_vertex_[i][2] = xyz_[3*i + 2];
         }
       } else {
          cout<<"VertexShader::loadVertex() is memory limit to 10000 points."<<endl;
          num_= -1;
       }
    }
    //theta degree pi/4 is cos(pi/4) = 1/sqrt(2)
    void setRotation(float theta_x, float theta_y, float theta_y){
      float cos_x, sin_x;
      float cos_y, sin_y;
      float cos_z, sin_z;
      mat4 rotateXMat = mat4(1.0);
      mat4 rotateYMat = mat4(1.0);
      mat4 rotateYMat = mat4(1.0);
      cos_x = cos(theta_x);
      sin_x = sin(theta_x);
      cos_y = cos(theta_y);
      sin_y = sin(theta_y);
      cos_z = cos(theta_z);
      sin_z = sin(theta_z);

      rotateXMat[1][1] = cos_x;
      rotateXMat[2][2] = cos_x;
      rotateXMat[1][2] = -sin_x;
      rotateXMat[2][1] =  sin_x;
    
      rotateYMat[0][0] = cos_x;
      rotateYMat[2][2] = cos_x;
      rotateYMat[0][2] = -sin_x;
      rotateYMat[2][0] =  sin_x;
    
      rotateZMat[0][0] = cos_x;
      rotateZMat[1][1] = cos_x;
      rotateZMat[0][1] = -sin_x;
      rotateZMat[1][0] =  sin_x;
    
      transMat_ = transMat_ * rotateXMat;           
      transMat_ = transMat_ * rotateYMat;           
      transMat_ = transMat_ * rotateYMat;           
      
    }
    void setTranslate(float t_x, float t_y, float t_z) {
      mat4 translateMat = mat4(1.0);
      translateMat[0][3] = t_x;
      translateMat[1][3] = t_y;
      translateMat[2][3] = t_z;
      transMat_ = transMat_ * translateMat;
 
    }
    void setScale(float s_x, float s_y, float s_z){
      mat4 scaleMat = mat4(1.0);
      scalMat[0][0] = s_x;
      scalMat[1][1] = s_y;
      scalMat[2][2] = s_z;
      transMat_ = transMat_ * scaleMat;

    }
    void setViewport(float n_x, float n_y){
      mat4 vpMat = mat4(1.0);
      vpMat[0][0] = n_x /2.0;
      vpMat[1][1] = n_y /2.0;
      vpMat[0][3] = (n_x - 1.0)/2.0;
      vpMat[1][3] = (n_y - 1.0)/2.0;
      //i am not sure to multiply with transMat_
      transMat_ = transMat_ * vpMat;
    }
    // x=l === left plane
    // x=r === right plane
    // y=b === bottom plane
    // y=t === top plane
    // z=n === near plane
    // z=f === far plane
    void setOrthProjection(float x_l, float x_r, float y_b, float y_t, float z_n, float z_f){
       mat4 orthMat = mat4(1.0);
       orthMat[0][0] = 2.0/(x_r - x_l);
       orthMat[1][1] = 2.0/(y_t - y_b);
       orthMat[2][2] = 2.0/(z_n - z_f);
       orthMat[0][3] = -(x_r + x_l) / (x_r - x_l);
       orthMat[1][3] = -(y_t + y_b) / (y_t - y_b);
       orthMat[2][3] = -(z_n + z_f) / (z_n - z_f); 
       transMat_ = transMat_ * orthMat;

    }
    void setPersProjection(float x_l, float x_r, float y_b, float y_t, float z_n, float z_f){
       mat4 persMat = mat4(0.0);
       persMat[0][0] = 2.0*z_n/(x_r - x_l);
       persMat[1][1] = 2.0*z_n/(y_t - y_b);
       persMat[2][2] = (z_n + z_f)/(z_n - z_f);

       persMat[0][3] = -(x_r + x_l) / (x_r - x_l);
       persMat[1][3] = -(y_t + y_b) / (y_t - y_b);
       persMat[2][3] = -(z_n + z_f) / (z_n - z_f);         

       transMat_ = transMat_ * persMat;
    }
    void setCameraTransform(float x_e, float y_e, float z_e){
       mat4 camMat = mat4(1.0);
       camMat[0][3] = -x_e;
       camMat[1][3] = -y_e;
       camMat[2][3] = -z_e;
       
       transMat_ = transMat_ * camMat;
    }
    void doTransform () {
       for(int i =0 ; i< num_ ; i++) {
         vec4 aff_vertex ;
         vec3 point =  (*in_vertex_[i]);
         aff_vertex[0]= point[0];
         aff_vertex[1]= point[1];
         aff_vertex[2]= point[2];
         aff_vertex[3]= 1.0;
         (*out_vertex_[i]) = transMat_ * aff_vertex;
         
       }
    }
    bool getLines(LineFormat lineMode, Line* lines, int* len) {
      if (lineMode == AdjacentPoint )  {
        (*len) = num_ -1;
      } else if (lineMode == ScatterPoint) {
        (*len) = num_ /2;
      }
      lines = new Line[*len];
      switch (lineMode) {
        case AdjacentPoint : 
          for (int i=0; i< num_ -1; i++) {
             lines[i]->setPoint(0, *out_vertex_[i]);
             lines[i]->setPoint(1, *out_vertex_[i+1]);
          }
          break;
        case ScatterPoint :
          for (int i=0; i< num_ -1 ; i= i+2) {
             lines[i]->setPoint(0, *out_vertex_[i]);
             lines[i]->setPoint(1, *out_vertex_[i+1]);
          }
          break;

        default : 

      }   

    }
    bool getTriangles () {

    }
    
  protected :
    vec4 gl_Position;
    string name_;
    //buffer all the vertex
    vec3* in_vertex_; //in order for debugging later, otherwise it only needs to keep one array of vertex.
    vec4* out_vertex_;
    mat4  transMat_; //transform matrix
    int   num_;
  };
}


#endif
